﻿namespace UCSD_BioLit
{
    #region Namespace

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    #endregion

    /// <summary>
    /// Class to hook up keyboard events. Word APIs do not expose keypress events. 
    /// This class helps us capture the right click keyboard shortcuts.
    /// </summary>
    public class KeyboardEventHook:IDisposable
    {
        /// <summary>
        /// Used to denote Keyboard event hooks type.
        /// </summary>
        private const int WH_KEYBOARD_LL = 13;

        /// <summary>
        /// Message ID for key down events
        /// </summary>
        private const int WM_KEYDOWN = 0x0100;

        /// <summary>
        /// Class name for exception title.
        /// </summary>
        private const string ExceptionTitle = "KeyboardEventHook";

        /// <summary>
        /// Initialization of delegate used to invoke.
        /// </summary>
        private LowLevelKeyboardProc _proc = null;

        /// <summary>
        /// hook ID object.
        /// </summary>
        private IntPtr _hookID = IntPtr.Zero;

        /// <summary>
        /// Delegate for the callback in keyboard events.
        /// </summary>
        /// <param name="nCode">Code for hook procedure to process</param>
        /// <param name="wParam">Specifies the virtual-key code of the key that generated the keystroke message</param>
        /// <param name="lParam">Specifies the repeat count</param>
        /// <returns></returns>
        internal delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Delegate referencing the callback procedure written for handling key press events.
        /// </summary>
        /// <param name="sender">The object which invoked the event.</param>
        /// <param name="e">The EventArgs.</param>
        public delegate void KeyboardRightClickDelegate(object sender, EventArgs e);

        /// <summary>
        /// Event to be fired in context menu helper class for handling right click context menu event.
        /// </summary>
        public event KeyboardRightClickDelegate KeyboardRightClickEvent;

        /// <summary>
        /// Flag to indicate the shift key press for right click shortcut context.
        /// </summary>
        private bool _isShiftKeyPressed;

        /// <summary>
        /// Variable to indicate that dispose is called.
        /// </summary>
        private bool disposed;

        /// <summary>
        /// Creates an instance of KeyboardEventHook.
        /// </summary>
        public KeyboardEventHook()
        {
            // call back procedure assigned to the delegate object.
            _proc = this.HookCallback;
        }

        /// <summary>
        /// Hooks the Keyboard event to the current process.
        /// </summary>
        internal void SetKeyBoardHook()
        {
            // un hook the existing hooks before adding the new one.
            this.ClearKeyBoardHook();

            try
            {
                // used to add the keyboard hook to the current running process.
                using (Process curProcess = Process.GetCurrentProcess())
                using (ProcessModule curModule = curProcess.MainModule)
                {
                    _hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _proc,
                        GetModuleHandle(curModule.ModuleName), 0);
                }
            }
            catch (Exception ex)
            {
                this.ClearKeyBoardHook();
                Debug.WriteLine(ex.Message, ExceptionTitle);
            }
        }

        /// <summary>
        /// Callback event for the Keypress event.
        /// </summary>
        /// <param name="nCode">Code for hook procedure to process</param>
        /// <param name="wParam">Specifies the virtual-key code of the key that generated the keystroke message</param>
        /// <param name="lParam">Specifies the repeat count</param>
        /// <returns></returns>
        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
                Keys currentKey = (Keys)vkCode;

                Debug.WriteLine(currentKey);

                // Check if the "Context Menu" key or "Shift+F10" was pressed.
                if (currentKey == Keys.Apps || (this._isShiftKeyPressed && currentKey == Keys.F10))
                {
                    this.KeyboardRightClickEvent(this, null);
                }
                else if (currentKey == Keys.RShiftKey || currentKey == Keys.LShiftKey)
                {
                    this._isShiftKeyPressed = true;
                }
                else
                {
                    this._isShiftKeyPressed = false;
                }
            }

            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        /// <summary>
        /// Releases the keyboard hook.
        /// </summary>
        internal void ClearKeyBoardHook()
        {
            if (_hookID != IntPtr.Zero)
            {
                bool ret = UnhookWindowsHookEx(_hookID);
                if (ret == false)
                {
                    return;
                }
                _hookID = IntPtr.Zero;
            }
        }

        #region Native Methods

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// The Dispose method helps to release the resources
        /// </summary>
        /// <param name="disposing">variable indicating if its an explicit call or implicit call</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                this.ClearKeyBoardHook();
                Debug.WriteLine("Released Keyboard hook.");
                this.disposed = true;
            }
        }

        #endregion
    }
}
